package com.hivclassifier.utils;

//DistanceMatrix.java
//
//(c) 1999-2001 PAL Development Core Team
//
//This package may be distributed under the
//terms of the Lesser GNU General Public License (LGPL)

import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;

/**
 * storage for pairwise distance matrices.
 * <p>
 * 
 * features: - printing in in PHYLIP format, - computation of (weighted) squared
 * distance to other distance matrix - Fills in all of array...
 * 
 * @version $Id: DistanceMatrix.java,v 1.9 2001/07/13 14:39:13 korbinian Exp $
 * 
 * @author Korbinian Strimmer
 * @author Alexei Drummond
 */
public class DistanceMatrix implements Serializable {

	/**
	 * This class has a hard-coded serialVersionUID all fields should be
	 * maintained under the same name for backwards compatibility with
	 * serialized objects.
	 */
	static final long serialVersionUID = 4725925229860707633L;

	//
	// Public stuff
	//

	/**
	 * number of sequences. Should be replaced by method that looks directly at
	 * size of distance matrix.
	 */
	public int numSeqs = 0;

	/** distances [seq1][seq2] */
	public double[][] distance = null;

	/**
	 * Consensus CCR5 sequence 
	 * */
	public static String consensusCcr5Sequence = "ctrpnnnt-rksihi--gpgqafya-tgdi-igdirqahc";
//	public static String consensusCcr5Sequence = "ctrpnnnt-rkxixi--gpgxafyx-tgxi-igdirqahc";

	/** constructor */
	public DistanceMatrix() {
	}

	/** constructor taking distances array and IdGroup */
	public DistanceMatrix(double[][] distance) {
		super();

		this.distance = distance;
		numSeqs = distance.length;
	}

	/**
	 * constructor that takes a distance matrix and clones the distances but
	 * uses the same idGroup.
	 */
	public DistanceMatrix(DistanceMatrix dm) {
		distance = new double[dm.getSize()][dm.getSize()];
		for (int i = 0; i < dm.getSize(); i++) {
			for (int j = 0; j < dm.getSize(); j++) {
				distance[i][j] = dm.distance[i][j];
			}
		}
		numSeqs = distance.length;
	}

	/** print alignment (PHYLIP format) */
	public void printPHYLIP(PrintWriter out) {
		// PHYLIP header line
		out.println("  " + numSeqs);

		for (int i = 0; i < numSeqs; i++) {
			out.print("      ");

			for (int j = 0; j < numSeqs; j++) {
				// Chunks of 6 blocks each
				if (j % 6 == 0 && j != 0) {
					out.println();
					out.print("                ");
				}

				out.print("  ");
			}
			out.println();
		}
	}

	/** returns representation of this alignment as a string */
	public String toString() {

		StringWriter sw = new StringWriter();
		printPHYLIP(new PrintWriter(sw));

		return sw.toString();
	}

	/** compute squared distance to second distance matrix */
	public double squaredDistance(DistanceMatrix mat, boolean weighted) {
		double sum = 0;
		for (int i = 0; i < numSeqs - 1; i++) {
			for (int j = i + 1; j < numSeqs; j++) {
				double diff = distance[i][j] - mat.distance[i][j];
				double weight;
				if (weighted) {
					// Fitch-Margoliash weight
					// (variances proportional to distances)
					weight = 1.0 / (distance[i][j] * distance[i][j]);
				} else {
					// Cavalli-Sforza-Edwards weight
					// (homogeneity of variances)
					weight = 1.0;
				}
				sum += weight * diff * diff;
			}
		}

		return 2.0 * sum; // we counted only half the matrix
	}

	/** compute absolute distance to second distance matrix */
	public double absoluteDistance(DistanceMatrix mat) {
		double sum = 0;
		for (int i = 0; i < numSeqs - 1; i++) {
			for (int j = i + 1; j < numSeqs; j++) {
				double diff = Math.abs(distance[i][j] - mat.distance[i][j]);

				sum += diff;
			}
		}

		return 2.0 * sum; // we counted only half the matrix
	}

	/**
	 * Returns the number of rows and columns that the distance matrix has.
	 */
	public int getSize() {
		return distance.length;
	}

	/**
	 * Returns the distances as a 2-dimensional array of doubles.
	 */
	public double[][] getDistances() {
		return distance;
	}

	/**
	 * Sets both upper and lower triangles.
	 */
	public void setDistance(int i, int j, double dist) {
		distance[i][j] = distance[j][i] = dist;
	}

	/**
	 * Adds a delta to both upper and lower triangle distances.
	 */
	public void addDistance(int i, int j, double delta) {
		distance[i][j] += delta;
		distance[j][i] += delta;
	}

	/**
	 * Returns the mean pairwise distance of this matrix
	 */
	public double meanDistance() {
		double dist = 0.0;
		int count = 0;
		for (int i = 0; i < distance.length; i++) {
			for (int j = 0; j < distance[i].length; j++) {
				if (i != j) {
					dist += distance[i][j];
					count += 1;
				}
			}
		}
		return dist / (double) count;
	}
}
